using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Compilation;
using System.CodeDom;
using System.Web;
using AspNetExtensions.Pages;
using System.Collections;
using System.Reflection;

namespace AspNetExtensions.ExpressionBuilders
{
    [ExpressionPrefix("ContextItem")]
    public class ContextObjectExpressionBuilder : ExpressionBuilder
    {

        // Parse the expression's arguments
        public override object ParseExpression(string expression, Type propertyType, ExpressionBuilderContext context)
        {
            return expression;
        }

        public override CodeExpression GetCodeExpression(
            System.Web.UI.BoundPropertyEntry entry, 
            object parsedData, 
            ExpressionBuilderContext context)
        {
            // Grab the name of the contextObject property
            if (!(parsedData is string))
                return null;

            string property = ((string)parsedData).Trim();
            
            // Must generate code for the right-side of an assignment
            // We make it call into a static method of this class

            // Define the CodeDOM object that represents the
            // parameter of the static method
            CodePrimitiveExpression prim = new CodePrimitiveExpression(property);
            CodePropertyReferenceExpression contextRef =
                new CodePropertyReferenceExpression(new CodeThisReferenceExpression(), "Context.Items");
            CodeExpression[] args = new CodeExpression[2] { prim, contextRef};

            // Define the CodeDOM object to represent the method invocation
            CodeTypeReferenceExpression refType;
            refType = new CodeTypeReferenceExpression(base.GetType());
            return new CodeMethodInvokeExpression(refType, "GetProperty", args);
        }

        public static object GetProperty(string propertyName, IDictionary contextObject)
        {
            // spllit the property using dot notation
            string[] propertyStrings = propertyName.Split('.');
            // Create a queue to keep track of the properties
            Queue<string> propertyStack = new Queue<string>(propertyStrings);
            
            // The first item referenced is the key in the dictionary
            string keyName = propertyStack.Dequeue();
            object keyObject = null;
            if (contextObject.Contains(keyName))
            {
                keyObject = contextObject[keyName];
                if (propertyStack.Count > 0)
                {   
                    keyObject = GetPropertyObject(keyObject, propertyStack);
                }
            }
            return keyObject;
        }

        public static object GetPropertyObject(object o, Queue<string> propertyNames)
        {
            if (propertyNames.Count == 0)
                return null;

            string propertyName = propertyNames.Dequeue();
            object propertyObject = null;

            // Get the object reference by the first property on the stack
            // .. First find out if the object has a property with the first name on the stack
            Type objectType = o.GetType();
            PropertyInfo propInfo = objectType.GetProperty(propertyName);
            if (propInfo != null)
            {
                propertyObject = propInfo.GetValue(o, null);
                goto GetChildProperties;
            }

            // .. Next find out if the object is a dictionary object, and get the object with that key
            if (o is IDictionary)
            {
                IDictionary dict = (IDictionary)o;
                if (dict.Contains(propertyName))
                {
                    propertyObject = dict[propertyName];
                    goto GetChildProperties;
                }
            }

            // Next find out if there is a non-void method with no parameters which has the same name
            MethodInfo method = objectType.GetMethod(propertyName, Type.EmptyTypes);
            if (method != null && method.ReturnType != typeof(void))
            {
                propertyObject = method.Invoke(o, null);
                goto GetChildProperties;
            }
            
            // If the property name is an integer, it could be an index to a list
            int index;
            if (Int32.TryParse(propertyName, out index))
            {
                if (o is IList)
                {
                    IList propertyList = (IList)o;
                    if (propertyList.Count > index)
                    {
                        propertyObject = propertyList[index];
                        goto GetChildProperties;
                    }
                }
            }

            // If we fell through to here, the property isn't discoverable
            return null;

            // If we were sent to this Goto label, the object was found. It could have child properties
            // if the propertyNames stack still has entries
            GetChildProperties:
            // If there are more properties on the stack, recursively get the object
            if (propertyNames.Count == 0)
                return propertyObject;
            else
                return GetPropertyObject(propertyObject, propertyNames);
        }
    }
}
